package com.book.modules.util;

import com.baomidou.dynamic.datasource.provider.DynamicDataSourceProvider;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.FileOutConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.TemplateConfig;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import com.book.common.io.PropertiesUtils;
import com.book.common.lang.Tuple4;
import com.google.common.collect.Maps;
import org.apache.commons.compress.utils.Lists;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.io.File;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Scanner;

/**
 * Description： scanner在IDEA中默认只能在main方法中使用
 * Help->Edit Custom VM Option->添加一行信息，重启IDEA
 * -Deditable.java.test.console=true
 *
 * @Author： leo.xiong
 * @CreateDate： 2019/12/25 10:02
 * @Email： leo.xiong@suyun360.com
 * Version：1.0
 */
@Component
public class CoreCodeGeneratorUtil {

    private static final Map<String, String> PUBLIC_INFO_MAP = Maps.newHashMap();
    /**
     * 项目路径，默认取本路径 D:/idea/work/
     */
    public static final String PROJECT_PATH = "projectPath";
    /**
     * 当前版本信息
     */
    public static final String VERSION = "version";
    /**
     * 创建用户邮箱
     */
    public static final String EMAIL = "email";
    /**
     * 创建用户名称
     */
    public static final String AUTHOR = "author";
    /**
     * 使用的数据库的dataSourceName，默认用base数据源
     */
    public static final String DATA_SOURCE_NAME = "dataSourceName";

    public static final String API_PROJECT_PATH = "apiProjectPath";
    public static final String YES = "YES";
    /**
     * 服务名称
     */
    public static final String APPLICATION_NAME = "applicationName";

    public static final String BASE_PATH = "basePath";

    @Autowired
    private DynamicDataSourceProvider dynamicDataSourceProvider;

    static {
        CoreCodeGeneratorUtil.initBuildPublicMap(null, null, null, null, null, null);
    }

    /**
     * 生成文件填充信息，如果无需修改，则使用默认值
     * 需要扩展的字段信息放于PUBLIC_INFO_MAP中
     * 在模板中#{cfg.key}此处的key就是expandMap的key显示的值就是expandMap的value,可以覆盖初始化值
     *
     * @param dataSourceName 数据库名称，不填默认使用主库
     * @param author         作者，不填默认使用自定义配置的author
     * @param email          邮箱，不填默认使用自定义配置的email
     * @param version        版本，不填默认使用自定义配置的version
     * @param projectPath   项目生成路径，可以使用默认
     * @param apiBasePath   API生成路径，可以使用默认
     */
    public static void initBuildPublicMap(String dataSourceName, String author, String email, String version, String projectPath, String apiBasePath) {
        PUBLIC_INFO_MAP.put(DATA_SOURCE_NAME, Optional.ofNullable(dataSourceName).orElse(PropertiesUtils.getInstance().getProperty("spring.datasource.dynamic.primary")));
        PUBLIC_INFO_MAP.put(AUTHOR, Optional.ofNullable(author).orElse(PropertiesUtils.getInstance().getProperty("custom.author")));
        PUBLIC_INFO_MAP.put(EMAIL, Optional.ofNullable(email).orElse(PropertiesUtils.getInstance().getProperty("custom.email")));
        PUBLIC_INFO_MAP.put(VERSION, Optional.ofNullable(version).orElse(PropertiesUtils.getInstance().getProperty("custom.version")));
        String project = System.getProperty("user.dir");
        PUBLIC_INFO_MAP.put(PROJECT_PATH, Optional.ofNullable(projectPath).orElse(project));
        PUBLIC_INFO_MAP.put(APPLICATION_NAME, PropertiesUtils.getInstance().getProperty("spring.application.name"));
        PUBLIC_INFO_MAP.put(BASE_PATH, PropertiesUtils.getInstance().getProperty("adminPath"));
        String projectName = PropertiesUtils.getInstance().getProperty("custom.project.name");
        String apiName = Optional.ofNullable(PropertiesUtils.getInstance().getProperty("custom.api.name")).orElse(projectName + "-" + PUBLIC_INFO_MAP.get(APPLICATION_NAME) + "-api");
        PUBLIC_INFO_MAP.put(API_PROJECT_PATH, Optional.ofNullable(apiBasePath).orElse(project.split(projectName)[0] + projectName) + "/book-interface-api/" + apiName);
    }

    /**
     * @param expandMap
     * @param expandMap
     */
    public static void coverPublicInfoMap(Map<String, String> expandMap) {
        if (!CollectionUtils.isEmpty(expandMap)) {
            PUBLIC_INFO_MAP.putAll(expandMap);
        }
    }

    /**
     * 自动生成文件
     *
     * @param parentModelPath 项目基础包路径 com.xxx.xx
     * @param tablePrefix     去掉表名前缀（无需去掉可填空字符串）
     */
    public void codeGenerator(String parentModelPath, String tablePrefix) throws Exception {
        DataSource dataSource = dynamicDataSourceProvider.loadDataSources().get(PUBLIC_INFO_MAP.get(DATA_SOURCE_NAME));
        if (dataSource == null) {
            throw new Exception("数据源名称不存在，请检查数据源名称是否正确");
        }
        do { // 代码生成器
            AutoGenerator mpg = new AutoGenerator();
            // 全局配置
            GlobalConfig gc = new GlobalConfig();
            //生成文件输出根目录
            String parentProjectPath = PUBLIC_INFO_MAP.get(PROJECT_PATH);
            String projectPath = parentProjectPath + "/src/main/java";
            gc.setOutputDir(projectPath);
            //作者
            gc.setAuthor(PUBLIC_INFO_MAP.get(AUTHOR));
            //生成完成后不弹出文件框
            gc.setOpen(false);
            //文件覆盖
            gc.setFileOverride(true);
            // 不需要ActiveRecord特性的请改为false
            gc.setActiveRecord(false);
            // XML 二级缓存
            gc.setEnableCache(false);
            // XML ResultMap
            gc.setBaseResultMap(true);
            // XML columList
            gc.setBaseColumnList(false);
            // gc.setSwagger2(true); 实体属性 Swagger2 注解
            mpg.setGlobalConfig(gc);
            // 数据源配置
            DataSourceConfig dsc = new DataSourceConfig();
            dsc.setDbType(DbType.MYSQL);
            Tuple4<String, String, String, String> tuple4 = getDbConfigInfoByDataSource(dataSource);
            dsc.setDriverName(tuple4._4());
            dsc.setUrl(tuple4._3());
            dsc.setUsername(tuple4._1());
            dsc.setPassword(tuple4._2());
            mpg.setDataSource(dsc);
            // 包配置
            PackageConfig pc = new PackageConfig();

            // 配置模板
            TemplateConfig templateConfig = new TemplateConfig();

            // 配置自定义输出模板
            //指定自定义模板路径，注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别
            templateConfig.setEntity("templates/entity");
            templateConfig.setService("templates/service");
            templateConfig.setServiceImpl("templates/serviceImpl");
            templateConfig.setMapper("templates/mapper");
            templateConfig.setController("templates/controller");
            templateConfig.setXml(null);
            mpg.setTemplate(templateConfig);

            // 策略配置
            StrategyConfig strategy = new StrategyConfig();
            strategy.setNaming(NamingStrategy.underline_to_camel);
            strategy.setColumnNaming(NamingStrategy.underline_to_camel);
            strategy.setEntityLombokModel(true);
            strategy.setRestControllerStyle(true);
            // 公共父类
            // 写于父类中的公共字段
            strategy.setSuperEntityColumns("id", "createBy", "createName", "createDate", "updateBy", "updateName", "updateDate", "remarks", "status", "version");
            strategy.setControllerMappingHyphenStyle(true);
            strategy.setTablePrefix(tablePrefix);
            mpg.setStrategy(strategy);
            mpg.setTemplateEngine(new FreemarkerTemplateEngine());
            String modelName = scanner("请输入模块名(生成文件夹名称)");
            if (modelName.contains(StringPool.DASH)) {
                modelName = scanner("请重新输入模块名，模块名不能包含下划线(生成文件夹名称)");
            }
            if (modelName.contains(StringPool.DOT)) {
                String[] modelNames = modelName.split(StringPool.BACK_SLASH + StringPool.DOT);
                String parentModelPathInfo = parentModelPath.replace(StringPool.DOT, StringPool.SLASH);
                for (int i = 0; i < modelNames.length; i++) {
                    if (i != modelNames.length - 1) {
                        File file = new File(projectPath + "/" + parentModelPathInfo + "/" + modelNames[i]);
                        if (!file.isDirectory()) {
                            file.mkdir();
                        }
                        parentModelPath = parentModelPath + StringPool.DOT + modelNames[i];
                    } else {
                        modelName = modelNames[i];
                    }
                }
            }
            pc.setModuleName(modelName);
            mpg.setPackageInfo(pc);
            pc.setParent(parentModelPath);
            String finalParentModelPath = parentModelPath;
            // 自定义输出配置
            List<FileOutConfig> focList = Lists.newArrayList();
            // 如果模板引擎是 freemarker,自定义配置会被优先输出
            focList.add(new FileOutConfig("/templates/mapperXml.ftl") {
                @Override
                public String outputFile(TableInfo tableInfo) {
                    // 自定义输出文件名 ， 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化！！
                    return parentProjectPath + "/src/main/resources/mapper/" + "/" + pc.getModuleName()
                            + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
                }
            });
            focList.add(new FileOutConfig("/templates/entity.vo.ftl") {
                @Override
                public String outputFile(TableInfo tableInfo) {
                    // 自定义输出文件名 ， 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化！！
                    return PUBLIC_INFO_MAP.get(API_PROJECT_PATH) + "/src/main/java/" + finalParentModelPath.replace(".", "/") + "/" + pc.getModuleName() + "/vo/"
                            + tableInfo.getEntityName() + "Vo" + StringPool.DOT_JAVA;
                }
            });

            focList.add(new FileOutConfig("/templates/entity.dto.ftl") {
                @Override
                public String outputFile(TableInfo tableInfo) {
                    // 自定义输出文件名 ， 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化！！
                    return PUBLIC_INFO_MAP.get(API_PROJECT_PATH) + "/src/main/java/" + finalParentModelPath.replace(".", "/") + "/" + pc.getModuleName() + "/dto/"
                            + tableInfo.getEntityName() + "Dto" + StringPool.DOT_JAVA;
                }
            });

            focList.add(new FileOutConfig("/templates/hystrix.ftl") {
                @Override
                public String outputFile(TableInfo tableInfo) {
                    // 自定义输出文件名 ， 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化！！
                    return PUBLIC_INFO_MAP.get(API_PROJECT_PATH) + "/src/main/java/" + finalParentModelPath.replace(".", "/") + "/" + pc.getModuleName() + "/hystrix/"
                            + "I" + tableInfo.getEntityName() + "Hystrix" + StringPool.DOT_JAVA;
                }
            });

            focList.add(new FileOutConfig("/templates/hystrixImpl.ftl") {
                @Override
                public String outputFile(TableInfo tableInfo) {
                    // 自定义输出文件名 ， 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化！！
                    return PUBLIC_INFO_MAP.get(API_PROJECT_PATH) + "/src/main/java/" + finalParentModelPath.replace(".", "/") + "/" + pc.getModuleName() + "/hystrix/impl/"
                            + tableInfo.getEntityName() + "HystrixImpl" + StringPool.DOT_JAVA;
                }
            });

            // 自定义配置
            InjectionConfig cfg = new InjectionConfig() {
                @Override
                public void initMap() {
                    Map<String, Object> customizeConfigMap = Maps.newHashMap();
                    customizeConfigMap.put("voPath", finalParentModelPath + "." + pc.getModuleName() + ".vo");
                    customizeConfigMap.put("dtoPath", finalParentModelPath + "." + pc.getModuleName() + ".dto");
                    customizeConfigMap.put("hystrixPath", finalParentModelPath + "." + pc.getModuleName() + ".hystrix");
                    customizeConfigMap.put("hystrixPathImpl", finalParentModelPath + "." + pc.getModuleName() + ".hystrix.impl");
                    customizeConfigMap.putAll(PUBLIC_INFO_MAP);
                    this.setMap(customizeConfigMap);
                }
            };
        /*
        cfg.setFileCreate(new IFileCreate() {
            @Override
            public boolean isCreate(ConfigBuilder configBuilder, FileType fileType, String filePath) {
                // 判断自定义文件夹是否需要创建
                checkDir("调用默认方法创建的目录");
                return false;
            }
        });
        */
            cfg.setFileOutConfigList(focList);
            mpg.setCfg(cfg);
            String tableName = scanner("请输入表名，多个英文逗号分割");
            if (tableName.contains(StringPool.DOT)) {
                tableName = scanner("请重新输入表名，表名不能包含英文句号，多个英文逗号分割");
            }
            strategy.setInclude(tableName.split(","));
            GeneratorCode generatorCode = new GeneratorCode(mpg);
            Thread thread = new Thread(generatorCode);
            thread.start();
        } while (!YES.equalsIgnoreCase(scanner("是否终止生成服务信息（YES OR NO）")));
    }

    /**
     * <p>
     * 读取控制台内容
     * </p>
     */
    private static String scanner(String tip) {
        Scanner scanner = new Scanner(System.in);
        StringBuilder help = new StringBuilder();
        help.append(tip + "：");
        System.out.println(help.toString());
        if (scanner.hasNext()) {
            String ipt = scanner.next();
            if (StringUtils.isNotEmpty(ipt)) {
                return ipt;
            }
        }
        throw new MybatisPlusException("请输入正确的提示信息");
    }

    /**
     * 获取连接信息
     *
     * @param dataSource
     * @return
     */
    private static Tuple4<String, String, String, String> getDbConfigInfoByDataSource(DataSource dataSource) {
        try {
            Class c = dataSource.getClass();
            Method passwordMethod = c.getMethod("getPassword", null);
            Method usernameMethod = c.getMethod("getUsername", null);
            Method urlMethod = c.getMethod("getJdbcUrl", null);
            Method driverClassNameMethod = c.getMethod("getDriverClassName", null);
            String password = passwordMethod.invoke(dataSource, null).toString();
            String username = usernameMethod.invoke(dataSource, null).toString();
            String url = urlMethod.invoke(dataSource, null).toString();
            String driverClassName = driverClassNameMethod.invoke(dataSource, null).toString();
            return new Tuple4<>(username, password, url, driverClassName);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

class GeneratorCode implements Runnable {

    private AutoGenerator mpg;

    public GeneratorCode(AutoGenerator mpg) {
        this.mpg = mpg;
    }

    @Override
    public void run() {
        mpg.execute();
    }
}
